package pt.mauromad.guias.service;

import static pt.mauromad.guias.utils.CastUtils.cast;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public abstract class DaoService<T, ID extends Serializable> {
	@Autowired
	@Qualifier("sessionFactory")
	protected LocalSessionFactoryBean sessionFactory;

	private final Class<T> persistentClass;
	private final Comparator<T> comparator;

	public DaoService(Class<T> entityClass) {
		this(entityClass, null);
	}

	public DaoService(Class<T> entityClass, Comparator<T> comparator) {
		this.persistentClass = entityClass;
		if (comparator == null && Comparable.class.isAssignableFrom(persistentClass))
			comparator = new Comparator<T>() {
				@Override
				public int compare(T o1, T o2) {
					Comparable<T> c1 = cast(o1);
					return c1.compareTo(o2);
				}
			};

		this.comparator = comparator;
	}

	protected Session getCurrentSession() {
		return sessionFactory.getObject().getCurrentSession();
	}

	public T save(T object) {
		saveConcrete(object);
		return object;
	}

	public Collection<T> save(Collection<T> list) {
		saveConcrete(list.toArray());
		return list;
	}

	protected void saveConcrete(Object... objects) {
		Session session = getCurrentSession();
		for (Object object : objects) {
			session.saveOrUpdate(object);
		}
	}

	public void delete(T object) {
		deleteConcrete(object);
	}

	protected void deleteConcrete(Collection<T> objects) {
		deleteConcrete(objects.toArray());
	}

	protected void deleteConcrete(Object... objects) {
		Session session = getCurrentSession();
		for (Object object : objects) {
			session.delete(object);
		}
	}

	public void delete(ID key) {
		T object = get(key);

		if (object == null) {
			return;
		}

		getCurrentSession().delete(object);
	}

	public T get(ID key) {
		return cast(getCurrentSession().get(persistentClass, key));
	}

	public List<T> all() {
		return list(null);
	}

	public T one(String query, Object... params) {
		try {
			return list(query, params).get(0);
		} catch (IndexOutOfBoundsException e) {
			return null;
		}
	}

	public List<T> list(String query, Object... params) {
		return list(getCurrentSession(), query, params);
	}

	protected List<T> list(Session session, String textualQuery, Object... params) {
		String fullTextualQuery = String.format("from %s", persistentClass.getName());

		if (textualQuery != null) {
			textualQuery = textualQuery.replaceAll(":", ":param");
			fullTextualQuery = String.format("%s %s", fullTextualQuery, textualQuery);
		}

		Query query = session.createQuery(String.format(fullTextualQuery, params));

		for (int i = 0; i < params.length; i++) {
			Object param = params[i];
			query.setParameter(String.format("param%d", i), param);
		}

		List<T> results = cast(query.list());

		if (results.size() == 0 || comparator == null) {
			return results;
		}

		Collections.sort(results, comparator);
		return results;
	}
}
